Repository navigation
Cross-table after (spec 0.5.0), a warning that suggests it, and integer keys 1..N, and release 1.16.0 - #49
Merged
Merged
Conversation
A table's single-column integer primary key was a shuffled sample starting at 0, so the first customers came out as 4251, 4557, 1941. It now numbers the rows 1..N like an identity column, and the rows come out in that order. A key drawn in an explicit min/max range or with a distribution, and an integer key that is also a foreign key (a one-to-one child keyed by its parent), keeps its draw and is put in ascending order: the key is drawn before any other column of the row, so sorting it sorts the rows. Text, uuid and composite keys are unchanged. Later days already continue from the largest key; an empty table now starts at 1 rather than 0. This is a deliberate output change under the per-version determinism promise: the key no longer draws from the table's stream, so every column after it moves. The pinned frames of earlier releases are pinned again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Half the orders of the ecommerce example were placed before their customer signed up. A table's creation column -- its first date or timestamp without `after` named for the created stage (created_at, signup_date, start_date), else its first named for no stage (order_date) -- is now kept on or after the creation column of every parent row its foreign keys point at, through a foreign key onto the parent's primary or unique key; with several parents, after the latest. A date and a timestamp compare by day. Self references, null foreign keys, parents without a date and foreign keys that break a cycle are not followed. The child keeps its own shape: a row dated before its parent takes another parent created by then, drawn from the parents the column already points at, so its date keeps the growth, seasonality and business hours it was drawn with. Only a row with no such parent (or a one-to-one, which cannot change parent) moves its date, drawn again from the column's day weights and hours cut to the part of the window on or after the parent's, never past as_of. Moving dates alone would have flattened the coffee webshop's gift-season peak (3.1x May before, 0.6x after); taking older parents keeps it (3.0-3.5x) at the cost of a steeper skew (top fifth of customers: 66% of orders before, about 80% after). A composite-key repair or a `when` fill that dates a row before its parent afterwards moves only that row's date and places its later dates again. On later days a new row whose parent was inserted the same day lands later that day. `after_parent: false` on the creation column turns the rule off for the table, and the generator rejects it anywhere else. The late_arriving defect prefers rows that stay clear of their parents once moved back, so it keeps breaking only the incremental model; a review test moves to a seed whose late rows still show what it checks. Co-Authored-By: Claude Opus 5.5 <[email protected]>
With hint tests on, each foreign key a creation date follows writes `model2data_not_before_parent` on the child's creation column: it joins the child to the parent model through the foreign key and returns the rows dated before the parent row, by day for a date against a timestamp. A key several parent rows share counts its earliest, so a duplicate_keys defect stays the unique test's to report. The macro ships in macros/model2data_parent_tests.sql, written only when a project has such a test, so other projects are what they were. `hint_tests_for` takes the model's refs beside bare tables, and the defects report checks the test the way its SQL reads, so a defect that dates a row before its parent lists it in EXPECTED_FAILURES.md. A dbt run confirms the test passes on clean data and fails on a customer created after its order. Co-Authored-By: Claude Opus 5.5 <[email protected]>
`after_parent` is new optional vocabulary, so by the spec's own rule it is a minor version: allowed only in a document that says `model2data: 0.5.0`, and an error naming the version to write in a 0.2, 0.3 or 0.4 document, which otherwise read exactly as before. It is a boolean on the table's creation column; anywhere else the reader names the column that is. A writer keeps the version a document was read with and writes 0.5.0 once it uses the hint; DBML with an `after_parent` note converts to 0.5.0. The spec README says what 0.5.0 also says of every document's data, as a minor release may before 1.0.0: a child's creation date is not before its parents' (References), and a one-column integer primary key numbers the rows 1..n in order (Keys). The schema's $id moves to /spec/0.5.0/, PUBLISHING.md lists the 0.5.0 files of engine 1.15.0 beside the older ones, and the guide pages name 0.5.0. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The changelog's Unreleased section says what 1.15.0 changes and which models generate different bytes for the same seed (every model with an integer primary key; where the parent rule applies, its children too). The README, LLMS.md and the time-shapes, distributions and dbt-project pages describe the rule, the opt-out and the new dbt test. The determinism digests are recorded again for these deliberate changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Tests reach the paths the rule's patch left uncovered: an empty child and an all-null creation column, a creation date that `when` fills after the rule ran (the row moves again and its later dates follow), the defects check with no parent frame or no rows, DBML with an `after_parent` note converting to 0.5.0, a model given the hint in code being written as 0.5.0, and an enum named like a date beside the creation column. Branches no input reaches are removed rather than kept: a datetime64 column (generation stores text and dates), a parent frame or foreign key that is missing (a rule only names generated parents), the guards around already-unboxed keys, a window start or end a parent can never pass, a rule without links, a null check `NaT` comparisons already answer, and a parent-less dbt test (`dbt_tests` always names its parent). Co-Authored-By: Claude Opus 5.5 <[email protected]>
A child's date no longer follows its parents' on its own, and the `after_parent` opt-out is gone. Instead `after` names what a date follows: a column of the same row as before, a parent's column as `<table>.<column>`, or a list of either. `after: customers.created_at` on orders.order_date keeps each order on or after the created_at of the customer its foreign key points at. The parent is reached through exactly one one-column foreign key onto its key; validation names what is wrong otherwise (no such table or column, not a date, no foreign key to it, only a composite one, one onto a column that is no key, the column's own table, and two foreign keys to the parent, named), and refuses tables whose afters name each other's columns. The generator refuses a parent drawn after the child. The technique that kept the stories stays: a late row takes an older parent from those the column points at, and only a row with none (or a one-to-one) moves its date within the column's own shape, never past as_of. A list mixing the row's own columns places the value after both. The fix-ups after `when` and composite-key repair, the later days, the by-day comparison of a date with a timestamp, the dbt test `model2data_not_before_parent` (one per parent column) and the late_arriving preference all follow the hint now. Validation warns where a child's first date can fall before a parent's created_at-like date and no `after` names that parent, with the hint to add as a structured `Issue.suggestion` (path, value, and the spec version an older document needs) for a one-click fix. No warning for a parent without such a date, a self-reference, or two foreign keys to one parent. A list or a parent's column in `after` is spec 0.5.0, gated as before. The bundled examples (and the spec's coffee-webshop example) use it where it is plainly right. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The changelog's Unreleased section, the README, LLMS.md and the time-shapes, distributions and dbt-project pages describe `after` naming a parent's column, its errors and the warning that suggests it, in place of the automatic rule. Dates change only for models that use it (the examples now do); ids change for every model with an integer primary key. The determinism digests are recorded again for these deliberate changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]> Spec 0.5.0 in the summary, the hint line, when to use it, how to act on the warning, and the two-foreign-keys case that is not built yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…o claude/parent-dates-and-ordered-ids # Conflicts: # CHANGELOG.md
…ed PyPI Co-Authored-By: Claude Opus 5.5 <[email protected]>
So a writer that orders hints the schema's way, such as the studio's, keeps the example byte for byte. Only the copied model file's digest changes; the data is the same. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
1.
aftercan name a parent's column.after: customers.created_atonorders.order_datekeeps every order on or after thecreated_atof the customer its foreign key points at.afternow takes a column of the same row (as before), a parent's column as<table>.<column>(the last.separates the column, soraw.customers.created_atworks), or a list of either, all of which the value is not before:after: [orders.ordered_at, products.launched_at],after: [placed_at, customers.created_at]. A date against a timestamp compares by day. A null FK constrains nothing.afters naming each other, a parent generated after the child (FK cycle broken at that key), and several FKs to the parent:tables.messages.columns.sent_at.generate.after: names users.created_at, and messages reaches users through more than one foreign key (sender_id, receiver_id):aftercannot tell which users row it meansas_of. A mixed list places the value after both. Holds afterwhenfills and composite-key repair, and on later days (a same-day parent lands earlier that day).afteris new vocabulary, so a document using it saysmodel2data: 0.5.0; a 0.2–0.4 document using it gets an error naming the version to write. DBML with such a note converts to 0.5.0.$id→/spec/0.5.0/; PUBLISHING.md lists the 0.5.0 files.model2data_not_before_parent(macro inmacros/model2data_parent_tests.sql, written only when needed). The defects report checks it;late_arrivingprefers rows that stay clear of their parents.2. A warning that suggests the hint. Where a table reaches a parent through one FK, the parent has a
created_at-like date, and noafterof the table names that parent, validation (and every run) warns:tables.orders.columns.order_date: can fall before customers.created_at (the customers row it points at through customer_id); addafter: customers.created_atto keep it after.The
Issuecarriessuggestion = Suggestion(path="tables.orders.columns.order_date.generate.after", value="customers.created_at", spec="0.5.0" when the document is older);valueis a tuple when it extends an existingafter. No warning for a parent without such a date, self-references, two FKs to one parent, or when anafteralready names it. Generation itself is unchanged by the warning.3. Integer primary keys number the rows 1..N, rows in key order (unchanged from the earlier commits of this PR).
The bundled examples (ecommerce, ecommerce_daily, ecommerce_training, advanced_features, saas_platform, and the spec's coffee_webshop) use the hint where it is plainly right and now say 0.5.0.
Determinism
Ids change for every model with an integer pk; dates change only for models that use a parent's column in
after. CHANGELOG (Unreleased) says so; determinism digests and the old pinned frames are re-recorded.Coffee-webshop story (studio example, local copy with
after: customers.created_atonorders.order_date; 2,000 customers / 10,000 orders, as_of 2026-10-07, seeds 1–3)skew: 0.8)Tests
tests/test_parent_dates.py(56 tests: generation, lists, mixed lists, stage chain, 1:1, last-day window,whenfix-up, days, every validation error, the cycle checks, the generator's own checks, the warning and its suggestion, DBML/writer versions, dbt tests and the defects check, keys 1..N); a realdbt buildpasses the parent test clean and fails it on a customer created after its order. Full suite 2232 passed; ruff, ruff format, ty clean.🤖 Generated with Claude Code